﻿// Inner Fire 游戏引擎库
// ObjectFactory - 游戏对象工厂
//
// Copyright (c) 2025 by 尔西大帝. All rights reserved.
//
// 所属层级：应用层（4）
// 用于创建各种不同类型的游戏对象
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2025-03-12

#include "object_factory.h"
#include "game/manager/scene.h"
#include <dx/mesh/dx_mesh_factory.h>
#include <dx/mesh/instanced_sprites.h>
#include <dx/mesh/skin_animator.h>
#include <game/manager/game_manager.h>
#include <game/resource/assets.h>

namespace ifire::game {

ObjectFactory::ObjectFactory(Scene* scene, GameManager* gm)
    : scene_(scene)
    , gm_(gm)
    , assets_(nullptr)
    , dx_(nullptr)
    , resources_(nullptr)
    , config_(nullptr) {}

void ObjectFactory::SetupEnvironment() {
  assets_ = gm_->GetAssets();
  config_ = gm_->GetConfig();
  dx_ = gm_->GetGraphics();
  resources_ = gm_->GetResources();
}

void ObjectFactory::FixedUpdate() {
  for (auto& e : objects_) {
    e->FixedUpdate();
  }
}

void ObjectFactory::LateUpdate() {
  for (auto& e : objects_) {
    e->LateUpdate();
  }
}

CameraObject* ObjectFactory::CreateCamera(
    ikit::sstr name_id, ikit::sstr prefab_name) {
  auto info = config_->GetPrefab(prefab_name);
  if (info == nullptr) {
    KERROR("*ObjectFactory* Unable find camera prefab: %s", //
        prefab_name.log_str());
    return nullptr;
  }

  auto obj = std::make_unique<CameraObject>(name_id, scene_, nullptr);

  auto ptr = obj.get();
  objects_.push_back(std::move(obj));
  return ptr;
}

InstancedSpriteObject* ObjectFactory::CreateInstancedSprite(ikit::sstr name_id,
    ikit::sstr type_name, ikit::sstr model_name, size_t count) {
  // 快捷访问
  auto& factory = dx::DxMeshFactory::GetInstance();
  auto& sprite = gm_->GetGraphics()->GetInstancedSprite();

  // 决定类型
  dx::InstancedSpriteType type = dx::ParseInstancedSpriteType(type_name);

  // 从系统中载入项目
  Model* model = nullptr;
  dx::DxMesh* mesh = factory.GetMesh(model_name);
  if (!mesh) {
    model = LoadModel(model_name);
    mesh = model ? model->GetMesh() : factory.GetQuad();
  }

  // 创建对象
  auto obj = std::make_unique<InstancedSpriteObject>(name_id, scene_, nullptr);

  // 从DirectX中创建一个项目并与其项目绑定
  auto pool = sprite.Create(type, mesh, count);
  obj->SetInstancedSprites(pool);

  // 如果拥有模型，则使用模型的贴图（必须要等到创建POOL对象之后进行）
  if (model) {
    obj->SetMaterial(PickMaterial(nullptr, model, 0)->GetMaterial());
  }

  auto ptr = obj.get();
  objects_.push_back(std::move(obj));
  return ptr;
}

GameObject* ObjectFactory::CreateSingleObject(const Prefab& info) {
  auto obj = CreateObjectPtr<StandardObject>(info);

  // 生成一个对象
  auto item = resources_->CreateRenderItem(info.render_layer);
  obj->AddRenderItem(item);

  // 设置项目信息
  item->SetTiling(info.tiling);
  item->SetWorldMatrix(obj->GetTransform().SetWorldMatrix(
      info.position, info.rotation, info.scale));
  item->cast_shadow = info.is_cast_shadow;

  // 设置贴图信息
  item->material = resources_->FindMaterial(info.material);

  // 配置Mesh信息
  auto& mesh = assets_->GetMesh(info.model);
  mesh.SetRenderObject(item);

  return obj;
}

GameObject* ObjectFactory::CreateSingleObject(ikit::sstr prefab_name) {
  auto prefab = config_->GetPrefab(prefab_name);
  if (prefab) {
    return CreateSingleObject(*prefab);
  } else {
    KLOG("CreateSingleObject: Unable find prefab: %s", prefab_name.log_str());
    return nullptr;
  }
}

GameObject* ObjectFactory::CreateStandardObject(
    Model* model, const Prefab& info) {
  auto obj = CreateObjectPtr<StandardObject>(info);
  obj->Reserve(model->GetMesh()->Submeshs().size());

  // 遍历子模型，并将其全部加上
  for (auto& sub : model->GetMesh()->Submeshs()) {
    // 得到一个渲染对象，通过材质决定RenderLayer。
    auto item = CreateAndSetRenderItem(info, model, sub);
    obj->AddRenderItem(item);
    // 配置Mesh信息
    sub.SetRenderObject(item);
  }

  // 一并设置世界矩阵
  // DEBUG：这里有一个问题，如果有N个不同的MESH在不同的位置？他们通过什么保存？
  obj->GetTransform().SetWorldMatrix(info.position, info.rotation, info.scale);

  return obj;
}

GameObject* ObjectFactory::CreateSkinMeshObject(
    Model* model, const Prefab& info) {
  auto obj = CreateObjectPtr<SkinMeshObject>(info);
  obj->SetModel(model);

  // 如果对象是骨骼动画对象，需要分配一个动画
  dx::SkinAnimator* animator = resources_->CreateAnimator();
  obj->SetAnimator(animator);
  animator->SetBoneCount(model->GetBoneCount());

  // 遍历子模型，并将其全部加上
  for (auto& sub : model->GetMesh()->Submeshs()) {
    // 得到一个渲染对象，通过材质决定RenderLayer。
    auto item = CreateAndSetRenderItem(info, model, sub);
    obj->AddRenderItem(item);
    // 设置类型为蒙皮骨骼（必须）
    item->type = dx::RenderItemType::SkinMesh;

    // 设置项目信息
    item->SetWorldMatrix(obj->GetTransform().SetWorldMatrix(
        info.position, info.rotation, info.scale));
    item->animator = animator;
    // 配置Mesh信息（这地方似乎要修改？怎么所有的东西共用这个？）
    sub.SetRenderObject(item);
  }

  return obj;
}

MapObject* ObjectFactory::CreateMapObject(ikit::sstr prefab_name) {
  auto info = config_->GetPrefab(prefab_name);
  if (info == nullptr) {
    KERROR("*ObjectFactory* Unable find map prefab: %s", //
        prefab_name.log_str());
    return nullptr;
  }

  auto obj = CreateObjectPtr<MapObject>(*info);

  // 载入地图的信息
  auto map_info = assets_->GetMap(info->map_name, true);
  if (!map_info) {
    KERROR("*ObjectFactory* Unable find prefab(%s)'s map: %s",
        info->name.log_str(), info->map_name.log_str());
    return nullptr;
  }
  // 载入地图的Setting
  auto map_setting = config_->GetMapSetting(map_info->setting);
  if (!map_setting) {
    KERROR("*ObjectFactory* Unable find prefab(%s)'s map setting: %s",
        info->name.log_str(), map_info->setting.log_str());
    return nullptr;
  }

  // 得到地图
  auto map = map_info->GetSimpleMap();
  obj->SetInfo(map, map_setting);

  // 获得地图的所有信息，决定哪些资源应该载入。
  eastl::vector<ModelLoader> models;
  models.reserve(10);
  AddTo(models, map_setting->terrain_model.name);

  // 启动项目
  resources_->ReadyCommand();

  // 载入资源
  LoadModels(models);
  // 如果Info指定了贴图，也要载入
  resources_->LoadMaterial(info->material);

  // 依次创建每个项目
  auto terrain_model = GetModel(models, map_setting->terrain_model.name);
  auto terrain_blocks =
      dx_->GetInstancedSprite().CreateMapBlocks(terrain_model->GetMesh(),
          map_setting->terrain_model.value, map->GetTerrainCount());
  // 设置贴图
  auto material = PickMaterial(info, terrain_model, 0);
  terrain_blocks->SetMaterial(material->GetMaterial());
  // 构造地图信息
  obj->SetTerrainBlocks(terrain_blocks);

  // 提交
  resources_->CommitCommand();

  return obj;
}

bool ObjectFactory::CheckValidated(void* model, const Prefab* prefab) {
  if (!model) {
    KERROR("*ObjectFactory* Unable find prefab(%s)'s model: %s",
        prefab->name.log_str(), prefab->model.log_str());
    // 这里需要提交命令，否则会出问题。
    resources_->CommitCommand();
    return false;
  } else {
    return true;
  }
}

Model* ObjectFactory::LoadModel(ikit::sstr model_name) {
  // 如果
  resources_->ReadyCommand();
  // 从Assets中得到模型
  auto* model = assets_->GetModel(model_name);
  if (!model) {
    KERROR("*ObjectFactory* Unable find model: %s", model_name.log_str());
    resources_->CommitCommand();
    return nullptr;
  }
  // 载入模型的相关资源
  resources_->LoadModel(model);
  resources_->CommitCommand();

  return model;
}

void ObjectFactory::LoadModels(eastl::vector<ModelLoader>& models) {
  for (auto& e : models) {
    // 从Assets中得到模型
    auto* model = assets_->GetModel(e.name);
    if (!model) {
      KERROR("*ObjectFactory* Unable find model: %s", e.name.log_str());
    } else {
      e.model = model;
    }
    // 载入模型的相关资源
    resources_->LoadModel(model);
  }
}

void ObjectFactory::AddTo(eastl::vector<ModelLoader>& models, ikit::sstr name) {
  for (auto& e : models) {
    if (e.name == name) {
      return;
    }
  }

  models.emplace_back(name);
}

Model* ObjectFactory::GetModel(
    const eastl::vector<ModelLoader>& models, ikit::sstr name) {
  for (auto& e : models) {
    if (e.name == name) {
      return e.model;
    }
  }
  return nullptr;
}

Material* ObjectFactory::PickMaterial(
    const Prefab* info, Model* model, int material_index) {
  // 得到贴图信息
  auto& material_info = model->GetMaterials().GetInfo(material_index);
  // 得到贴图
  Material* material = nullptr;
  // 如果没有配置单独的Material，则使用Object的Material
  if (material_info.empty()) {
    if (info) {
      material = assets_->GetMaterial(info->material);
    } else {
      material = assets_->GetDefaultMaterial();
    }
  } else {
    material = assets_->GetMaterial(material_info.material);
  }
  return material;
}

dx::RenderItem* ObjectFactory::CreateAndSetRenderItem(
    const Prefab& info, Model* model, const dx::DxSubmesh& sub) {
  // 得到贴图材质
  Material* material = PickMaterial(&info, model, sub.material_index);

  // 这里进行自动判断，不需要传递项目
  bool is_skin_mesh = info.type == PrefabType::SkinMesh;
  // 创建对象
  auto item = resources_->CreateRenderItem( //
      material->GetRenderLayer(), is_skin_mesh);

  // 设置名称方便调试
  item->instanced_name = info.name;

  // 设置其它共用信息
  item->SetTiling(info.tiling);
  item->cast_shadow = info.is_cast_shadow;
  item->material = material->GetMaterial();
  item->animator = nullptr;
  return item;
}

GameObject* ObjectFactory::CreateObject(ikit::sstr name) {
  // 获得项目
  auto prefab = config_->GetPrefab(name);
  if (prefab == nullptr) {
    KERROR("*ObjectFactory* Unable find prefab: %s", name.log_str());
    return nullptr;
  }

  // 启动载入
  resources_->ReadyCommand();

  // 载入贴图（属于通用）
  if (!prefab->material.empty()) {
    resources_->LoadMaterial(prefab->material);
  }

  // 从Assets中得到模型
  auto* model = assets_->GetModel(prefab->model);
  if (!CheckValidated(model, prefab)) {
    return nullptr;
  }
  // 载入模型的相关资源
  resources_->LoadModel(model);

  // 根据类型不同处理
  // 水体和天空盒需要处理的处理。
  GameObject* obj = nullptr;
  if (prefab->type == PrefabType::Mesh) {
    obj = CreateStandardObject(model, *prefab);
  } else if (prefab->type == PrefabType::SkinMesh) {
    obj = CreateSkinMeshObject(model, *prefab);
  } else if (prefab->type == PrefabType::SkyBox ||
             prefab->type == PrefabType::Water) {
    obj = CreateSingleObject(*prefab);
  }

  // 提交命令
  resources_->CommitCommand();

  return obj;
}

GameObject* ObjectFactory::FindObject(ikit::sstr name) {
  for (auto& e : objects_) {
    if (e->GetName() == name) {
      return e.get();
    }
  }
  return nullptr;
}

} // namespace ifire::game